﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Windows;
using System.Windows.Media;

namespace OpenLS.Spreadsheet.UI.Internals
{
    internal partial class ClusteredCellTextControl : ClusteredCellControl
    {
        public static readonly DependencyProperty EditedCellProperty =
            DependencyProperty.Register("EditedCell", typeof (EditedCell), typeof (ClusteredCellTextControl),
                                        new UIPropertyMetadata(null, (d, e) =>
                                                                         {
                                                                             var clusteredCellTextControl =
                                                                                 (ClusteredCellTextControl) d;
                                                                             clusteredCellTextControl.
                                                                                 OnEditedCellChanged(e);
                                                                         }));

#if DEBUG
        private int counter = 0;
#endif

        private readonly VisualCollection<CellClusterTextElement> Clusters;

        private readonly Dictionary<CellClusterKey, CellClusterTextElement> clusterTable =
            new Dictionary<CellClusterKey, CellClusterTextElement>();

        private readonly List<CellClusterTextElement> recycledElements = new List<CellClusterTextElement>();
        private CellsLeftCurtain _leftCurtain;
        private Point _origin;
        private bool wasUnloaded;

        public ClusteredCellTextControl()
        {
            Clusters = new VisualCollection<CellClusterTextElement>(this);
            Unloaded += ClusteredCellTextControl_Unloaded;
            Loaded += ClusteredCellTextControl_loaded;
            base.SizeChanged += ClusteredCellTextControl_SizeChanged;
        }

        private Point Origin
        {
            get { return _origin; }
            set
            {
                if (_origin == value)
                    return;
                _origin = value;
                var t = RenderTransform as TranslateTransform;
                if (t == null)
                {
                    t = new TranslateTransform();

                    RenderTransform = t;
                }
                t.X = -value.X;
                t.Y = -value.Y;
                Clip = new RectangleGeometry(new Rect(Origin.X, Origin.Y, ActualWidth, ActualHeight));
            }
        }

        public EditedCell EditedCell
        {
            get { return (EditedCell) GetValue(EditedCellProperty); }
            set { SetValue(EditedCellProperty, value); }
        }

        protected override int VisualChildrenCount
        {
            get { return Clusters.Count; }
        }

        private RangeAddress RangeAddress
        {
            get
            {
                if (WorksheetPane is WorksheetPane)
                    return ((WorksheetPane) WorksheetPane).RangeAddress;
                if (WorksheetPane is WorksheetPage)
                    return ((WorksheetPage) WorksheetPane).RangeAddress;
                throw new NotSupportedException();
            }
        }

        private CellsLeftCurtain LeftCurtain
        {
            get { return _leftCurtain; }
            set
            {
                if (value == _leftCurtain)
                    return;
                if (_leftCurtain != null)
                {
                    _leftCurtain.CellsAdded -= _leftCurtain_CellsAdded;
                    _leftCurtain.CellsRemoved -= _leftCurtain_CellsRemoved;
                    _leftCurtain.Dispose();
                }
                _leftCurtain = value;
                if (_leftCurtain != null)
                {
                    _leftCurtain.CellsAdded += _leftCurtain_CellsAdded;
                    _leftCurtain.CellsRemoved += _leftCurtain_CellsRemoved;

                    foreach (var k in _leftCurtain.GetClusterKeys())
                        EnsureCluster(k);
                }
            }
        }

        private void ClusteredCellTextControl_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            Clip = new RectangleGeometry(new Rect(Origin.X, Origin.Y, ActualWidth, ActualHeight));
        }


        private void ClusteredCellTextControl_loaded(object sender, RoutedEventArgs e)
        {
            if (wasUnloaded)
            {
                IWorksheetFragment f = WorksheetPane;
                SetupFragment(f);
            }
            wasUnloaded = false;
        }

        private void SetupFragment(IWorksheetFragment f)
        {
            if (f != null)
            {
                AddCells(f.GetCells());
            }
        }

        private void ClusteredCellTextControl_Unloaded(object sender, RoutedEventArgs e)
        {
            
            clear();
            wasUnloaded = true;
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            if
                (WorksheetPane == null)
                return new Size();

            Origin = GetAbsoluteLocation(new CellAddress(WorksheetPane.Top, WorksheetPane.Left));
            foreach (CellClusterTextElement cluster in Clusters)
            {
                CellClusterKey key = cluster.Key;
                Point topLect = GetAbsoluteLocation(new CellAddress(key.Row, key.Column));
                Point bottomRight =
                    GetAbsoluteLocation(new CellAddress(key.Row + CellClusterKey.Size, key.Column + CellClusterKey.Size));
                // Size size = new Size(bottomRight.X - topLect.X, bottomRight.Y - topLect.Y);
                //  Point pt = new Point(topLect.X, topLect.Y);
                var rect = new Rect(topLect, bottomRight);
                cluster.Arrange(rect);
            }
            return finalSize;
        }


        // Using a DependencyProperty as the backing store for LastArrangeRect.  This enables animation, styling, binding, etc...

        private void OnEditedCellChanged(DependencyPropertyChangedEventArgs e)
        {
            var value = (EditedCell) e.OldValue;
            if (value != null && value.Cell != null)
                InvalidateChild(value.Cell);
            value = (EditedCell) e.NewValue;
            if (value != null && value.Cell != null)
                InvalidateChild(value.Cell);
        }

        private void InvalidateChild(Cell cell)
        {
            foreach (CellClusterKey key in CellClusterKey.GetKeys(cell))
            {
                CellClusterTextElement control;
                if (clusterTable.TryGetValue(key, out control))
                    control.InvalidateSelf();
            }
        }


        protected override bool IsRenderProperty(string name)
        {
            return false;
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            foreach (CellClusterTextElement elements in Clusters)
                elements.Measure(availableSize);
            return new Size();
        }

        protected override Visual GetVisualChild(int index)
        {
            return Clusters[index];
        }


        internal override void OnWorksheetPaneChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            onPaneChanged(e);
        }

        private void _leftCurtain_CellsRemoved(object sender, CellListEventArgs e)
        {
            var p = WorksheetPane as WorksheetPane;
            if (p != null)
            {
                foreach (var k in GetClustersFromCells(e.Items))
                {
                    if (!p.Window.Intersects(k))
                        RemoveCluster(k);
                }
            }

        }

        private void _leftCurtain_CellsAdded(object sender, ListEventArgs<Cell> e)
        {
            foreach (var k in GetClustersFromCells(e.Items))
                EnsureCluster(k);
        }

        private static IEnumerable<CellClusterKey> GetClustersFromCells(IEnumerable<Cell> items)
        {
            Contract.Assert(Enumerable.All(items, c => c != null));
            return Enumerable.Distinct(
                Enumerable.Select(
                    Enumerable.SelectMany(items, c => c.GetRangeAddress().GetCellAddresses()), //\\ null here
                    a => new CellClusterKey(a)));
        }

        private void onPaneChanged(DependencyPropertyChangedEventArgs e)
        {
            _layoutManager = null;
            clear();
            var newValue = e.NewValue as WorksheetPane;
            var oldValue = e.OldValue as WorksheetPane;
            if (oldValue != null)
            {
                clear();
                oldValue.Worksheet.LayoutChanged -= WorksheetLayoutChanged;
                oldValue.Rows.CollectionChanged -= onRowCollectionChanged;
                oldValue.Columns.CollectionChanged -= onColumnCollectionChanged;
                oldValue.Window.ClustersAdded -= OnClustersAdded;
                oldValue.Window.ClustersRemoved -= OnClustersRemoved;
                oldValue.Window.Cleared -= OnClusterCleared;
                oldValue.Window.VerticalChanged -= WindowChanged;
                oldValue.Window.HorizontalChanged -= WindowChanged;

                LeftCurtain = null;
            }
            var f = (IWorksheetFragment) e.NewValue;
            if (f == null)
                return;
            SetupFragment(f);
            if (newValue == null)
                return;
            //value.cellsRecomputeDisabled = true;
            newValue.Rows.CollectionChanged += onRowCollectionChanged;
            newValue.Columns.CollectionChanged += onColumnCollectionChanged;
            newValue.Worksheet.LayoutChanged += WorksheetLayoutChanged;
            newValue.Window.ClustersAdded += OnClustersAdded;
            newValue.Window.ClustersRemoved += OnClustersRemoved;
            newValue.Window.Cleared += OnClusterCleared;
            newValue.Window.VerticalChanged += new EventHandler<CellsWindow.BoundsChangedEventArgs>(WindowChanged);
            newValue.Window.HorizontalChanged += new EventHandler<CellsWindow.BoundsChangedEventArgs>(WindowChanged);
            LeftCurtain = new CellsLeftCurtain(newValue.Worksheet, new IntInterval(newValue.Top, newValue.Bottom),
                                               newValue.Left);

            InvalidateSelf(null);
        }

        void WindowChanged(object sender, CellsWindow.BoundsChangedEventArgs e)
        {
            CellsWindow w = (CellsWindow) sender;
            LeftCurtain.ColumnIndex = w.Left;
            LeftCurtain.RowInterval = new IntInterval(w.Top, w.Bottom);
        }


        private void OnClustersRemoved(object sender, ListEventArgs<CellClusterKey> e)
        {
            List<CellClusterKey> curtainKeys = Enumerable.ToList(this.LeftCurtain.GetClusterKeys());
            foreach (CellClusterKey key in Enumerable.Where(e.Items, k => !curtainKeys.Contains(k)))
                RemoveCluster(key);
        }

        private void OnClusterCleared(object sender, EventArgs e)
        {
            _layoutManager = null;
            foreach (CellClusterKey key in Enumerable.ToList(clusterTable.Keys))
                RemoveCluster(key);
        }

        private void OnClustersAdded(object sender, ListEventArgs<CellClusterKey> e)
        {
            _layoutManager = null;
            foreach (CellClusterKey key in e.GetItems())
                EnsureCluster(key);
        }


        private void onRowCollectionChanged(object sender, NotifyCollectionChangedEventArgs arg)
        {
            InvalidateSelf(null);
        }

        private void RemoveCluster(CellClusterKey key)
        {
            CellClusterTextElement item;
            if (!clusterTable.TryGetValue(key, out item))
                return;
            if (item.IsRecycled)
            {
                return;
            }
            item.IsRecycled = true;
            recycledElements.Add(item);
            while (recycledElements.Count > 50 && recycledElements.Count > clusterTable.Count*0.95)
            {
                CellClusterTextElement toDestroy = recycledElements[0];
                recycledElements.RemoveAt(0);
                DestroyCluster(toDestroy);
            }
        }

        private void DestroyCluster(CellClusterTextElement item)
        {
            Contract.Requires(item != null);
            item.Dispose();
            clusterTable.Remove(item.Key);
            Clusters.Remove(item);
        }

        private void onColumnCollectionChanged(object sender, NotifyCollectionChangedEventArgs arg)
        {
            InvalidateSelf(null);
        }

        private void WorksheetLayoutChanged(object sender, EventArgs e)
        {
            _layoutManager = null;
            Dimension dim = null;
            DimensionEventArgs args = (DimensionEventArgs) e;
            InvalidateSelf(args.Dimension);
        }

        internal override void InvalidateSelf(Dimension dimension)
        {
            base.InvalidateSelf(dimension);

            if (dimension != null)
            {
                foreach (var cluster in this.clusterTable.Values)
                {
                    if (cluster.ContainsDimension(dimension))
                    {
                        Contract.Assert(cluster.ContainsDimension(dimension));
                        cluster.InvalidateVisual();
                    }
                }
            }
            InvalidateArrange();
        }


        protected override void OnCellCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
#if DEBUG
#endif

            base.OnCellCollectionChanged(sender, e);
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    break;
                case NotifyCollectionChangedAction.Remove:
                    break;
                case NotifyCollectionChangedAction.Reset:
                    break;
                default:
                    throw new NotImplementedException();
            }
        }

        private static IEnumerable<T> MakeEnumerable<T>(IEnumerable source)
        {
            foreach (T item in source)
            {
                yield return item;
            }
        }

        private void AddCells(IEnumerable<Cell> cells)
        {
            IEnumerable<CellClusterKey> keys =
                Enumerable.Distinct(Enumerable.SelectMany(Enumerable.Where(cells, c => c != null),
                                                          c => CellClusterKey.GetKeys(c)));
            foreach (CellClusterKey key in keys)
            {
                CellClusterTextElement cluster = EnsureCluster(key);
            }
        }

        private void clear()
        {
            foreach (CellClusterTextElement c in Clusters)
                c.Dispose();
            Clusters.Clear();
            clusterTable.Clear();
        }


        private CellClusterTextElement EnsureCluster(CellClusterKey key)
        {
            Contract.Ensures(Contract.Result<CellClusterTextElement>() != null);
            Contract.Ensures(!Contract.Result<CellClusterTextElement>().IsRecycled);
            CellClusterTextElement cluster;
            if (!clusterTable.TryGetValue(key, out cluster))
            {
                cluster = new CellClusterTextElement(this, key, Worksheet);
                clusterTable.Add(key, cluster);
                Clusters.Add(cluster);
                InvalidateArrange();
            }

            if (cluster.IsRecycled)
            {
                cluster.IsRecycled = false;
                recycledElements.Remove(cluster);
            }

            return cluster;
        }

        internal void FlushDirtyOverflowRows()
        {
            if (_dirtyOverflowRows.Count == 0)
                return;
            IEnumerable<CellClusterKey> keys = Enumerable.Where(clusterTable.Keys,
                                                                k => Enumerable.Any(_dirtyOverflowRows, r => k.Row == r));
            List<CellClusterTextElement> dirtyOverflowClusters =
                Enumerable.ToList(Enumerable.Where(Enumerable.Select(keys, k => clusterTable[k]), c => c.Overflows));
            _dirtyOverflowRows.Clear();
            foreach (CellClusterTextElement cluster in dirtyOverflowClusters)
                cluster.InvalidateSelf();
        }

        public void OnCellLayoutValueChanged(Cell cell)
        {
            if (_layoutManager != null)
            {
                if (cell == null)
                {
                    _layoutManager = null;
                    return;
                }
                else
                {
                    _layoutManager.OnCellChanged(cell);
                }
            }
            if (cell != null)
            {
                for (int i = cell.Row.Index; i < cell.Row.Index + cell.RowSpan; i++)
                {
                    int r = CellClusterKey.GetRounded(i);
                    if (!_dirtyOverflowRows.Contains(r))
                        _dirtyOverflowRows.Add(r);
                }
            }
        }
    }
}